﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using Krile.Common;
using Krile.Kernel;
using Krile.Kernel.Control;
using Krile.Kernel.Control.Surface.MessageObjects;
using Krile.Kernel.Control.Surface;
using Krile.Kernel.Data;
using Krile.Kernel.WindowsEx;

namespace Wassrile.Operation.Web
{
    public class StatusReceiver : IDisposable
    {
        private bool Initialized = false;
        Timer timer;
        public StatusReceiver()
        {
            timer = new Timer(
                new TimerCallback(Tick),
                null,
                Timeout.Infinite,
                Timeout.Infinite);
        }

        public void Init()
        {
            if (!Initialized) return;
            timer.Change((long)0, (long)Core.Config.Receive.recvInterval * 1000);
        }

        public void Suspend()
        {
            timer.Change((long)Timeout.Infinite, (long)Core.Config.Receive.recvInterval * 1000);
        }

        public void Dispose()
        {
            timer.Dispose();
        }

        const string TimelineURI = "http://api.wassr.jp/statuses/friends_timeline.json";
        const string PublicTimelineURI = "http://api.wassr.jp/statuses/public_timeline.json";
        static string MyTimelineURI
        {
            get
            {
                return "http://api.wassr.jp/statuses/user_timeline.json?id=" + Core.Config.account.UserName;
            }
        }
        const string ReplyForMeURI = "http://api.wassr.jp/statuses/replies.json";
        const string ChannelFeedURI = "http://api.wassr.jp/channel_message/list.json?name_en=";
        static string GetChannelListURI
        {
            get { return "http://api.wassr.jp/channel_user/user_list.json?login_id=" + Core.Config.account.UserName; }
        }
        const string PagePrefix = "?page=";

        DateTime prevDt = DateTime.Now;
        private void Tick(object o)
        {
            if (!Initialized)
            {
                Suspend();
                return;
            }
            DoReceive();
        }

        public void DoReceive()
        {
            bool checkReplyFeed = false;
            var span = DateTime.Now - prevDt;
            if (span.Seconds >= Core.Config.Receive.replyCheckInterval)
            {
                prevDt = DateTime.Now;
                checkReplyFeed = true;
            }
            ReceiveTimeline(
                TimelineURI,
                Core.Config.Receive.getHistoryNum,
                Core.Config.Receive.traceFav);
            Thread.Sleep(5000);
            ReceiveTimeline(
                MyTimelineURI,
                1,
                Core.Config.Receive.traceFav);
            Thread.Sleep(5000);
            if (Core.Plugin.WOperation.Status.RecvGlobalTimeline)
            {
                ReceiveTimeline(
                    PublicTimelineURI,
                    Core.Config.Detail.Extra.PublicTimelineRecvIdx,
                    true);
                Thread.Sleep(5000);
            }
            if (checkReplyFeed)
            {
                ReceiveTimeline(
                    ReplyForMeURI,
                Core.Config.Receive.getHistoryNum,
                Core.Config.Receive.traceFav);
                Thread.Sleep(5000);
            }

            //ch
            ReceiveCh(true);
        }

        //受信できない場合がある:↓
        //const string TestURI = "http://api.wassr.jp/statuses/show.json";
        const string GetFriendsURI = "http://api.wassr.jp/statuses/friends.json";
        const string GetFollowersURI = "http://api.wassr.jp/statuses/followers.json";
        public void InitialReceive()
        {
            System.Diagnostics.Debug.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■");
            System.Diagnostics.Debug.WriteLine("■ Wassrile : INITIALIZING...");
            Initialized = false;
            //check account enability
            //正当なアカウントにもかかわらず認識されない場合がある
            /*
            if (!Core.Config.Detail.SkipAccountChecking)
            {
                var test = Web.Connect.WebOperation(new Uri(TestURI), "GET");
                if (!test.Succeeded)
                {
                    Bridge.DebugReporter.AddReport("[Wassr]アカウントの正当性を確認できませんでした。再設定するか、Krileを再起動してください。");
                    return;
                }
            }
            */
            //Get users data and set following data state
            var ret = Web.Connect.DownloadWithCallback<User[]>
                (new Uri(GetFriendsURI),
                true,
                true,
                new NetConnect.DStreamCallback<User[]>(Web.ReadJson.GetUsers),
                "GET");
            if (ret.Succeeded && ret.Data != null)
            {
                foreach (var d in ret.Data)
                {
                    d.Following = true;
                }
            }
            Thread.Sleep(5000);
            Web.Connect.DownloadWithCallback<User[]>
                (new Uri(GetFollowersURI),
                true,
                true,
                new NetConnect.DStreamCallback<User[]>(Web.ReadJson.GetUsers),
                "GET");
            if (Core.Config.Detail.BootReceives.myTL)
            {
                ReceiveTimeline(
                    MyTimelineURI,
                    1,
                    false, false);
                Thread.Sleep(5000);
            }
            if (Core.Config.Detail.BootReceives.replyTL)
            {
                ReceiveTimeline(
                    ReplyForMeURI,
                    Core.Config.Detail.BootReceives.replyTLPageNum,
                    false, false);
                Thread.Sleep(5000);
            }
            if (Core.Config.Detail.BootReceives.friendTL)
            {
                ReceiveTimeline(
                    TimelineURI,
                    Core.Config.Detail.BootReceives.friendTLPageNum,
                    false);
                Thread.Sleep(5000);
            }
            ReceiveCh(Core.Config.Detail.BootReceives.channelFeed, false);
            Initialized = true;
            Init();
            System.Diagnostics.Debug.WriteLine("■ Wassrile : INITIALIZED!");
            System.Diagnostics.Debug.WriteLine("■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■");
        }

        private void ReceiveCh(bool recvChTL)
        {
            ReceiveCh(recvChTL, true);
        }

        private void ReceiveCh(bool recvChTL, bool isnew)
        {
            var opResult = Web.Connect.DownloadWithCallback<Dictionary<string, KeyValuePair<string, DateTime>>>
                (new Uri(GetChannelListURI),
                true,
                true,
                new NetConnect.DStreamCallback<Dictionary<string, KeyValuePair<string, DateTime>>>(Web.ReadJson.GetChList),
                "GET");
            if (!opResult.Succeeded || opResult.Data == null)
            {
                Bridge.DebugReporter.AddReport(new System.Net.WebException("Wassr:受信エラー(" + opResult.OperationUri.OriginalString + ")"));
                return;
            }
            List<string> updated = new List<string>();
            foreach (var k in opResult.Data.Keys)
            {
                if (DataMan.IsChannelRecvRequired(k, opResult.Data[k].Key, opResult.Data[k].Value))
                    updated.Add(k);
            }
            if (updated.Count > 0 && (recvChTL || !String.IsNullOrEmpty(Core.Config.Detail.InitialReceiveChId)))
            {
                string[] ids = Core.Config.Detail.InitialReceiveChId.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var k in updated)
                {
                    if (recvChTL || ids.Contains<string>(k))
                    {
                        ReceiveChTimeline(
                            ChannelFeedURI + k,
                            1,
                            false, isnew);
                    }
                }
            }
        }

        public void ReceiveTimeline(string uri, uint pageTraceIndex, bool forced)
        {
            ReceiveTimeline(uri, pageTraceIndex, forced, true);
        }

        public void ReceiveTimeline(string uri, uint pageTraceIndex, bool forced, bool isnew)
        {
            for (uint p = 1; p <= pageTraceIndex; p++)
            {
                if (!Receive(
                    uri,
                    p,
                    new NetConnect.DStreamCallback<Wassrile.Operation.DataClasses.WStatus[]>(Web.ReadJson.GetWassrStatus), isnew)
                    && !forced)
                {
                    break;
                }
            }
        }

        public void ReceiveChTimeline(string uri, uint pageTraceIndex, bool forced, bool isnew)
        {
            for (uint p = 1; p <= pageTraceIndex; p++)
            {
                if (!Receive(
                    uri,
                    p,
                    new NetConnect.DStreamCallback<Wassrile.Operation.DataClasses.WStatus[]>(Web.ReadJson.GetWassrStatusFromChFeed), isnew) &&
                    !forced)
                {
                    break;
                }
            }
        }

        private bool Receive(
            string uri,
            uint curPage,
            NetConnect.DStreamCallback<DataClasses.WStatus[]> callback,
            bool isnew)
        {
            try
            {
                if (curPage > 1)
                    uri += PagePrefix + curPage.ToString();
                var opResult = Web.Connect.DownloadWithCallback<DataClasses.WStatus[]>
                    (new Uri(uri),
                    true,
                    true,
                    callback,
                    "GET");
                if (!opResult.Succeeded || opResult.Data == null)
                {
                    Bridge.DebugReporter.AddReport(new System.Net.WebException("Wassr:受信エラー(" + opResult.OperationUri.OriginalString + ")"));
                    return false;
                }
                if (opResult.Data.Length == 0)
                    //no data
                    return false;
                if (Web.DataMan.WidExists(opResult.Data[opResult.Data.Length - 1].Id))
                    return false;
                StatusTrapper(opResult.Data, isnew);
                return true;
            }
            catch (NullReferenceException nre)
            {
                MessageBoxEx.Show("Wassr 受信エラー", "設定が壊れている可能性があります。" + Environment.NewLine + nre.ToString() + "Wassr 受信エラー", MessageBoxEx.MessageBoxExButtons.Close, MessageBoxEx.MessageBoxExIcon.Error);
                return false;
            }
        }

        private void StatusTrapper(DataClasses.WStatus[] statuses, bool isnew)
        {
            List<string> idStore = new List<string>();
            Func<DataClasses.WStatus, DataClasses.WStatus> func = (n) => { idStore.Add(n.Id); return DecorateMessage(n); };
            var iteration = from ws in statuses
                            where !idStore.Contains(ws.Id)
                            select func(ws);
            DataMan.RegistNewStatus(iteration, isnew);
        }

        /// <summary>
        /// Status message decorator
        /// </summary>
        /// <param name="ws"></param>
        /// <returns></returns>
        private DataClasses.WStatus DecorateMessage(DataClasses.WStatus ws)
        {
            ws.Text.Original = ws.Text.Original.Replace('\r', ' ').Replace('\n', ' ');
            List<MessageObject> moStock = new List<MessageObject>();
            if (!String.IsNullOrEmpty(ws.channelId))
            {
                moStock.Add(new Controls.ChLabel(ws.channelId, ws.channelName));
                moStock.Add(new Text(" "));
            }
            moStock.AddRange(CreateMsgObject(ws.Text.Original));
            if (ws.LinkedImage != null)
            {
                moStock.Add(new Image(ws.LinkedImage));
            }
            var favd = new Controls.FavDigest(ref ws);
            ws.digest = favd;
            moStock.Add(favd);
            ws.Text.MessageObjects = moStock.ToArray();
            if (ws.LocationName != null)
            {
                if (ws.Text.FooterText == String.Empty || ws.Text.FooterText == null)
                    ws.Text.FooterText = "[L:" + ws.LocationName + "]";
                else
                    ws.Text.FooterText = "[L:" + ws.LocationName + "] " + ws.Text.FooterText;
            }
            return ws;
        }

        Regex PictoRegex = new Regex(@"([\uE001-\uF0FF])", RegexOptions.Singleline | RegexOptions.Compiled);
        Regex ColorRegex = new Regex(@"{color:(aqua|blue|chocolate|darkblue|fuchsia|gold|gray|green|lightgreen|lime|olive|orange|pink|darkred|plum|purple|red|silver|skyblue):(.*?)}", RegexOptions.Singleline | RegexOptions.Compiled);

        const string WassrProfilePage = "http://wassr.jp/user/";
        private MessageObject[] CreateMsgObject(string msg)
        {
            try
            {
                msg = msg.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace("\r", "").Replace("\n", "");
                //pre-processor
                msg = ColorRegex.Replace(msg, "<#$1>$2<");
                msg = PictoRegex.Replace(msg, "<*>$1<");
                msg = Krile.Common.Regexs.URLRegex.Replace(msg, "<$1>$1<");
                msg = Krile.Common.Regexs.AtRegex.Replace(msg, "@<" + WassrProfilePage + "$1>$1<");
                List<MessageObject> objStock = new List<MessageObject>();
                foreach (var item in msg.Split(new[] { '<' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    if (item.Contains('>'))
                    {
                        var iArray = item.Split('>');
                        switch (iArray[0][0])
                        {
                            case '*':
                                //pictogram
                                objStock.Add(new Controls.Pictogram(Convert.ToString((int)iArray[1][0], 16).ToUpper()));
                                continue;
                            case '#':
                                //colored string
                                objStock.Add(
                                    new Text(
                                        iArray[1].Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&"),
                                        System.Drawing.Color.FromName(iArray[0].Substring(1)))
                                        );
                                continue;
                            default:
                                //link
                                var target = iArray[0].Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&");
                                objStock.Add(new Hyperlink(
                                    target,
                                    iArray[1].Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&")));
                                if (Core.Config.Detail.extractGyazo &&
                                    target.StartsWith("http://gyazo.com"))
                                {
                                    try
                                    {
                                        objStock.Add(new Image(new Uri(target)));
                                        Bridge.ImageManager.ReserveImage(new Uri(target), false);
                                    }
                                    catch (UriFormatException) { }
                                }
                                continue;
                        }
                    }
                    objStock.Add(new Text(item.Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&")));
                }
                return objStock.ToArray();
            }
            catch (IndexOutOfRangeException ior)
            {
                Bridge.DebugReporter.AddReport("Index out of range: text>" + msg + Environment.NewLine + ior.ToString());
                return new[] { new Text("[ Message generate error ] raw:" + msg) };
            }
        }
    }
}
